import java.io.BufferedReader;
import java.io.FileReader;

/*
 * class Parser Parses the tokens returned from the lexical analyzer. Update the
 * parser implementation to parse the Decaf language according to the grammar
 * provided.
 */
public class Parser
{

	private Lexer lexer; // lexical analyzer
	private Token token; // look ahead token
	private String fileName;

	public Parser( Lexer lex, String inFile )
	{
		lexer = lex;
		fileName = inFile;
	}

	public ClassDecl parse() throws SyntaxException
	{

		MethodDecls mds = new MethodDecls();

		token = lexer.nextToken();

		match( Token.KW, "class" );
		String classId = token.getLexeme();
		match( Token.ID );
		match( token.LB );

		ClassDecl cd = new ClassDecl( classId, mds );

		if( token.getTokenType() == Token.RB )
		{
			match( token.RB );
			return cd;
		}

		while( token.getTokenType() != Token.RB )
		{
			mds.addMethodDecl( methodDecl() );
		}
		match( token.RB );

		return cd;
	}

	private MethodDecl methodDecl() throws SyntaxException
	{
		match( token.KW, "static" );
		String methodType = token.getLexeme();
		type();
		String methodName = "";
		if( token.getTokenType() == Token.ID )
			methodName = token.getLexeme();
		
		int tempLineNumber = lexer.lineNumber;
		int tempCharNumber = lexer.charNumber;
		String tempLine = getLine( tempLineNumber );
		DataInfo methodInfo = new DataInfo( tempLineNumber, tempCharNumber-methodName.length(), tempLine );
		
		match( Token.ID );
		match( Token.LP );
		FormalParams fps = formalParams();
		match( token.RP );
		Block block = block();
		MethodDecl md = new MethodDecl( methodName, methodType, fps, block,methodInfo );

		return md;

	}

	private FormalParams formalParams() throws SyntaxException
	{
		FormalParams value = null;
		if( token.getTokenType() == Token.KW )
		{
			value = properFormalParams();
		}
		return value;

	}

	private FormalParams properFormalParams() throws SyntaxException
	{
		FormalParams result = new FormalParams();
		result.add( formalParam() );
		result.merge( properFormalParamshelper() );
		return (result);

	}

	private FormalParams properFormalParamshelper() throws SyntaxException
	{
		FormalParams result = new FormalParams();
		if( token.getTokenType() == token.FA )
		{
			match( token.FA );
			result.add( formalParam() );
			result.merge( properFormalParamshelper() );
			return (result);
		}
		return (null);
	}

	private FormalParam formalParam() throws SyntaxException
	{
		String paramType = token.getLexeme();
		type();
		String paramName = token.getLexeme();
		DataInfo di= new DataInfo(lexer.lineNumber,lexer.charNumber-paramName.length(),getLine( lexer.lineNumber ));
		match( token.ID );
		return (new FormalParam( paramName, paramType, di ));

	}

	private Block block() throws SyntaxException
	{

		Block returnedBlock = new Block();
		match( token.LB );
		statements( returnedBlock );
		match( token.RB );
		return returnedBlock;

	}

	private void statements( Block b ) throws SyntaxException
	{

		if( token.getTokenType() == token.LB )
		{
			b.add( block() );
			statements( b );
		}

		if( token.getTokenType() == token.ID )
		{

			String StatementName = token.getLexeme();
			DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber-StatementName.length(),getLine(lexer.lineNumber));
			match( token.ID );
			match( token.AO );
			Expression exp = expression();
			match( token.SM );
			b.add( new AssignStatement( StatementName, exp, di ) );
			statements( b );

		}
		if( token.getTokenType() == token.KW )
		{
			if( isType() )
			{
				b.add( localVarDecl() );
				statements( b );
			}
			if( token.getLexeme().equals( "if" ) )
			{
				b.add( ifStmt() );
				statements( b );
			}
			if( token.getLexeme().equals( "while" ) )
			{
				b.add( whileStmt() );
				statements( b );
			}
			if(token.getLexeme().equals( "return" ))
			{
				b.add(returnStmt());
				statements( b );
			}

		}
		return;

	}



	private Statement statement() throws SyntaxException
	{
		
		if( token.getTokenType() == token.LB )
		{
			return block();
		}

		if( token.getTokenType() == token.ID )
		{

			String StatementName = token.getLexeme();
			DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber-StatementName.length(),getLine(lexer.lineNumber));
			match( token.ID );
			match( token.AO );
			Expression exp = expression();
			match( token.SM );
			return new AssignStatement( StatementName, exp , di);

		}
		
		if( token.getTokenType() == token.KW )
		{
			if( isType() )
			{
				
				return  localVarDecl() ;
			}
			if( token.getLexeme().equals( "if" ) )
			{
				return ifStmt() ;
			}
			if( token.getLexeme().equals( "while" ) )
			{
				
				return whileStmt() ;
			}
			if( token.getLexeme().equals( "return" ) )
			{
				
				return returnStmt() ;
			}
			
		}
		
		throw new SyntaxException(lexer.lineNumber,lexer.charNumber,token.getLexeme(),"A statement",getLine(lexer.lineNumber));
	}

	private Statement ifStmt() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber-2,getLine(lexer.lineNumber));
		match( token.KW, "if" );
		match( token.LP );
		Expression expr = expression();
		match( token.RP );
		Statement statement = statement();
		Statement elseStatement = null;

		if( token.getTokenType() == Token.KW && token.getLexeme().equals( "else" ) )
		{
			match( token.KW, "else" );
			elseStatement = statement();
		}
		return new IfStmt( expr, statement, elseStatement, di );

	}
	
	private Statement whileStmt() throws SyntaxException
	{
		
		match(token.KW,"while");
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		match(token.LP);
		Expression expr = expression();
		match(token.RP);
		Statement st = statement();
		
		return(new WhileStmt(expr, st, di));
	}
	
	private Statement returnStmt() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		match(token.KW, "return");
		Expression expr = expression();
		match(token.SM);
		return new ReturnStmt(expr, di);
	}

	private Statement localVarDecl() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		String varType = token.getLexeme();
		type();
		String varName = token.getLexeme();
		match( token.ID );
		match( token.SM );
		return (new LocalVarDecl( varType, varName,di ));

	}

	private Expression expression() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		Expression value = condAndExpr();
		if(token.getTokenType() == Token.LO)
		{
			match(Token.LO);
			return new Expression((CondAndExpression) value,1 , expression(),di);
		}
		return value;
	}
	
	private CondAndExpression condAndExpr() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		CondAndExpression value = eqExpr();
		if(token.getTokenType()==Token.LA)
		{
			match(Token.LA);
			return new CondAndExpression( (EqualExpression)value, 1, condAndExpr(),di );
		}
		
		return value;
	}
	
	private EqualExpression eqExpr() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		EqualExpression value = addExpr();
		if(token.getTokenType()==Token.EQ)
		{
			match(Token.EQ);
			return new EqualExpression( (AddExpression)value, 1, eqExpr(),di );
		}
		if(token.getTokenType()==Token.NE)
		{
			match(Token.NE);
			return new EqualExpression( (AddExpression)value, 2, eqExpr(),di );
		}
		return value;
	}
	
	private AddExpression addExpr() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		AddExpression value = multExpr();
		if(token.getTokenType() == Token.PO)
		{
			match(Token.PO);
			return new AddExpression((MultExpression) value, 1, addExpr(), di );
		}
		if(token.getTokenType() == Token.MO)
		{
			match(Token.MO);
			return new AddExpression((MultExpression) value, 2, addExpr(), di );
		}
		return value;
		
	}
	
	private MultExpression multExpr() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		PrimaryExpression value = primaryExpr();
		if(token.getTokenType() == Token.MD)
		{
			match(token.MD);
			return new MultExpression(value,3,multExpr(), di);
		}
		if(token.getTokenType() == Token.DO)
		{
			match(token.DO);
			return new MultExpression(value,2,multExpr(),di );
		}
		if(token.getTokenType() == Token.TO)
		{
			match(token.TO);
			return new MultExpression(value,1,multExpr(),di );
		}
		return value;
	}
	
	private PrimaryExpression primaryExpr() throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		String value = token.getLexeme();
	//	System.out.println(value);
		if(token.getTokenType()==Token.NM)
		{
	//		System.out.println(token.getTokenType()==token.NM);
	//		String type = "int";
			match(Token.NM);
	//		if(value.contains("."))
	//			type = "float";
			PrimaryExpression exp = new PrimaryExpression( "NM", value, null, null,di );
	//		exp.nmType = type;
			return exp;
		}
		
		if(token.getTokenType()==Token.BL)
		{
			match(Token.BL);
			return new PrimaryExpression( "BL", value, null, null,di );
		}
		
		if(token.getTokenType()==Token.ST)
		{
			match(Token.ST);
			return new PrimaryExpression( "ST", value, null, null,di );
		}
		
		if(token.getTokenType()==Token.ID)
		{
			value = token.getLexeme();
			match(Token.ID);
			if(token.getTokenType()!=Token.LP)
			{
				return new PrimaryExpression( "ID", value, null, null,di );
			}
			else
			{
				
				CallExpr callExpr= callExpr(value);
				return new PrimaryExpression(null,null,callExpr,null,di);
			}
		}
		
		if(token.getTokenType() == Token.LP)
		{
			match(Token.LP);
			Expression expr = expression();
			match(Token.RP);
			
			return new PrimaryExpression(null,null,null,expr,di);
		}
		
		throw new SyntaxException( lexer.lineNumber,lexer.charNumber,token.getLexeme(),"An expression",getLine( lexer.lineNumber ));
	}
	
	private CallExpr callExpr(String callName) throws SyntaxException
	{
		DataInfo di = new DataInfo(lexer.lineNumber,lexer.charNumber,getLine(lexer.lineNumber));
		match(Token.LP);
		CallExpr ce= new CallExpr(callName,di);
		if(token.getTokenType()!=Token.RP)
		{
			getActualParams(ce);
		}
		
		match(Token.RP);
		return ce;
		
	}
	
	private void getActualParams(CallExpr callExpr) throws SyntaxException
	{
		Expression expr = expression();
		callExpr.addToList( expr );
		if(token.getTokenType() == Token.FA)
		{
			match(Token.FA);
			getActualParams( callExpr );
		}
		else
		return;
	}

	private void match( int t ) throws SyntaxException
	{
		if( token.getTokenType() == t )
		{
			token = lexer.nextToken();
		}
		else
		{
			throw new SyntaxException(lexer.lineNumber,lexer.charNumber,token.getLexeme(), getLexeme(t),getLine(lexer.lineNumber) );
		}
	}

	private void match( int t, String s ) throws SyntaxException
	{
		if( token.getTokenType() == t && token.getLexeme().equals( s ) )
		{
			token = lexer.nextToken();
		}
		else
		{
			
			throw new SyntaxException(lexer.lineNumber,lexer.charNumber,token.getLexeme(),s,getLine(lexer.lineNumber) );
		}
	}

	private void type() throws SyntaxException
	{
		if( token.getLexeme().equals( "int" ) )
		{
			match( Token.KW, "int" );
			return;
		}
		if( token.getLexeme().equals( "float" ) )
		{
			match( Token.KW, "float" );
			return;
		}
		if( token.getLexeme().equals( "String" ) )
		{
			match( Token.KW, "String" );
			return;
		}
		if( token.getLexeme().equals( "boolean" ) )
		{
			match( Token.KW, "boolean" );
			return;
		}
		String temp = "A type (either int, float, String or boolean )";
		throw new SyntaxException( lexer.lineNumber,lexer.charNumber,token.getLexeme(),temp,getLine(lexer.lineNumber) );
	}

	private boolean isType()
	{

		String s = token.getLexeme();
		return (s.equals( "int" ) || s.equals( "float" ) || s.equals( "boolean" ) || s.equals( "String" ));

	}
	
	private String getLexeme(int t)
	{
		switch(t)
		{
			case 0:
				return "End of file";
			case 1:
				return ";";
			case 2:
				return "+";
			case 3:
				return "-";
			case 4:
				return "*";
			case 5:
				return "/";
			case 6:
				return "A number";
			case 7:
				return "A keyword";
			case 8:
				return "true or false";
			case 9:
				return "A String lateral";
			case 10:
				return "An identifier";
			case 11:
				return ",";
			case 12:
				return "(";
			case 13:
				return ")";
			case 14:
				return "{";
			case 15:
				return "}";
			case 16:
				return "=";
			case 17:
				return "==";
			case 18:
				return "!=";
			case 19:
				return "||";
			case 20:
				return "&&";
			case 21:
				return "%";
			case 22:
				return "An error";
		}
		return "ERROR";
	}
	
	public String getLine(int number)
	{
		try
		{
			BufferedReader r = new BufferedReader(new FileReader(fileName));
			for(int i = 1 ; i< number ; i++)
			{
				r.readLine();
			}
			return r.readLine();
		}
		catch( Exception e )
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return "NOTHING";
	}
}
